/*****************************************************************************
 *  CommLayerLib - DataWriter.java
 *  Copyright (C) 2012  Matteo Piscitelli
 *  E-mail: matteo@picciux.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  For further info, check http://code.google.com/p/java-comm-layer/
 *
 *  SVN: $Id: DataWriter.java 32 2012-10-09 18:20:32Z picciux@gmail.com $
 *  
 *****************************************************************************/

package it.picciux.commlayer;


import java.io.*;

/**
 * Abstract class to implement objects that write data for a {@link CommLayer}.
 * DataWriter provides a separate thread to actually do the write job, so it will 
 * not block calling thread operations.
 * @author Matteo Piscitelli
 * @see CommLayer
 */
public abstract class DataWriter extends Thread implements IDataWriter {
	protected OutputStream s;
	protected int bufferSize = CommLayer.DEFAULT_BUFFER_SIZE;
	protected int[] buffer = new int[bufferSize];
	protected int dataToWrite = 0;
	protected boolean keepRunning = false;
	protected boolean haveToWait;
	protected ICommEventListener eventListener;
	
	public final static String THREAD_NAME = "DataWriter";
	
	public DataWriter() {
		s = null;
		eventListener = null;
	}

	public DataWriter(OutputStream outputStream) {
		s = outputStream;
		eventListener = null;
	}
	
	public void setOutputStream(OutputStream outputStream) {
		this.s = outputStream;
	}
	
	/**
	 * @return the eventListener
	 */
	public ICommEventListener getEventListener() {
		return eventListener;
	}

	/**
	 * @param eventListener the eventListener to set
	 */
	public void setEventListener(ICommEventListener eventListener) {
		this.eventListener = eventListener;
	}

	/**
	 * Abstract to be implemented. Successors of this class should use this method to
	 * set thread name (if platform allows it) or other thread-relevant things.
	 */
	protected abstract void setThreadData();

	public void start (OutputStream outputStream) {
		if (isRunning()) return;
		this.s = outputStream;
		start();
	}
	
	public void start() {
		if (isRunning()) return;
		setThreadData();
		keepRunning = true;
		super.start();
	}
	
	public synchronized void writeData(int data) {
		buffer[0] = data;
		dataToWrite = 1;
		haveToWait = false;
		notify();
	}
	
	public synchronized int writeData(int[] data) {
		return writeData(data, 0, data.length);
	}

	public synchronized int writeData(int[] data, int offset, int count) {
		int written = Math.min(bufferSize, count);
		
		for (int i = 0; i < written; i++)
			buffer[i] = data[i + offset];

		dataToWrite = written;
		haveToWait = false;
		notify();
		return written;
	}

	public synchronized int writeData(byte[] data) {
		return writeData(data, 0 , data.length);
	}
	
	public synchronized int writeData(byte[] data, int offset, int count) {
		int written = Math.min(bufferSize, count);

		for (int i = 0; i < written; i++)
			buffer[i] = (data[i + offset] & 0xFF);
	
		dataToWrite = written;
		haveToWait = false;
		notify();
		return written;
	}
	
	
	public synchronized int writeText(String text) {
		return writeData(text.getBytes());
		/*
		buffer = new int[tb.length];
		for (int i = 0; i < buffer.length; i++)
			buffer[i] = tb[i];
		
		haveToWait = false;
		notify();
		*/
	}
	
	protected synchronized boolean isRunning() {
		return keepRunning;
	}
	
	protected synchronized void terminate() {
		haveToWait = false;
		keepRunning = false;
		notify();
	}
	
	protected synchronized int[] getFromBuffer() {
		//if (buffer == null) return null;
		if (dataToWrite == 0) return null;
		
		int[] ret = new int[dataToWrite];
		for (int i = 0; i < dataToWrite; i++)
			ret[i] = buffer[i];
		
		dataToWrite = 0;
		return ret;
	}
	
	protected synchronized void threadWait() {
		haveToWait = true;
		
		while (haveToWait) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}
		
	// Thread code
	public void run() {
		int[] local;
		
		while (isRunning()) {
			if (s == null) {
				terminate();
				break;
			}
			
			local = getFromBuffer();
			if (local == null) {
				threadWait(); //wait for buffer
			} else {
				// buffer has data: write it!
				for (int i = 0; i < local.length; i++)
					try {
						s.write(local[i]);
					} catch (IOException e) {
						// stream closed!
						s = null;

						if (eventListener != null)
							eventListener.commLayerEvent(CommLayer.WRITE_FAILED, e.getMessage());
					}
				
				if ((eventListener != null) && (s != null))
					eventListener.commLayerEvent(CommLayer.WRITE_COMPLETED, null);
			}
		}
		
	}

	/**
	 * @return the bufferSize
	 */
	public int getBufferSize() {
		return bufferSize;
	}

	/**
	 * @param bufferSize the bufferSize to set
	 */
	public void setBufferSize(int bufferSize) {
		if (! isRunning()) {
			this.bufferSize = bufferSize;
			buffer = new int[bufferSize];
			dataToWrite = 0;
		}
	}

}
